package org.belkodevelop.geocoding.service;

import java.io.*;
import java.net.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.belkodevelop.geocoding.dao.GeoCacheDAO;
import org.belkodevelop.geocoding.domen.GeoCache;
import org.belkodevelop.geocoding.domen.GeoStatus;
import org.belkodevelop.geocoding.exception.GeoCodingConnectionException;
import org.belkodevelop.geocoding.exception.GeoCodingJSONParseException;
import org.belkodevelop.geocoding.exception.GeoCodingUnsupportedEncodingException;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class GeoCodingService {

    private final Log log = LogFactory.getLog(getClass());

    @Value("${web.geocode.url_api}")
    private final String urlApiGeocode = null;
    
    @Autowired
    private GeoCacheDAO geoCacheDAO;
    
    private String createRequest(Map<String, String> params) throws GeoCodingUnsupportedEncodingException {
        StringBuilder requestParams = new StringBuilder();
        Iterator<String> paramIterator = params.keySet().iterator();
        try {
            while (paramIterator.hasNext()) {
                String key = paramIterator.next();
                String value = params.get(key);
                requestParams.append(URLEncoder.encode(key, "UTF-8"));
                requestParams.append("=").append(URLEncoder.encode(value, "UTF-8"));
                requestParams.append("&");
            }
        } catch(UnsupportedEncodingException ex) {
            throw new GeoCodingUnsupportedEncodingException("error create URL", ex);
        }
        return requestParams.toString();
    }
    
    private GeoStatus  getStatusResponce(String responce) throws GeoCodingJSONParseException {
        Map<String, Object> map = getMapJson(responce);
        return GeoStatus.valueOf((String)map.get("status"));
    }
        
    
    private Map<String, Object> getMapJson(String string) throws GeoCodingJSONParseException {
        ObjectMapper mapper = new ObjectMapper();
        try {
            Map<String, Object> responceMap = mapper.readValue(string, 
                    new TypeReference<Map<String, Object>>() {});
            return responceMap;

        } catch (JsonGenerationException ex) {
            throw new GeoCodingJSONParseException(ex);
        } catch (JsonMappingException ex) {
            throw new GeoCodingJSONParseException(ex);
        } catch (IOException ex) {
            throw new GeoCodingJSONParseException(ex);
        }
    }
    @Transactional(rollbackFor=Exception.class)
    public Map<String, Object> sendRequest(String adress, boolean sensor, String region, String language) 
            throws GeoCodingUnsupportedEncodingException, GeoCodingConnectionException, GeoCodingJSONParseException {
        Map<String, String> requestParams = new HashMap<>();
        requestParams.put("address", adress);
        requestParams.put("sensor", Boolean.toString(sensor));
        requestParams.put("region", region);
        requestParams.put("language", language);
        
        String request = createRequest(requestParams);
        GeoCache geoCache = geoCacheDAO.getCache(request);
        if (geoCache != null) {
            return getMapJson(geoCache.getResponce());
        }
        // делаем запрос к сервису гокодирования
        String responce = sendRequest(request);
        // сохраняем результат геокодирования
        GeoStatus geoStatus = getStatusResponce(responce);
        if (geoStatus == GeoStatus.OK || geoStatus == GeoStatus.ZERO_RESULTS) {
            geoCache = new GeoCache();
            geoCache.setStatus(geoStatus.toString());
            geoCache.setRequest(request);
            geoCache.setResponce(responce);
            geoCacheDAO.addCache(geoCache);
        }
        // возращаем текст ответа
        return getMapJson(responce);
    }
    
    private String sendRequest(String request) throws GeoCodingConnectionException, GeoCodingJSONParseException {
        try {
            URL url = new URL(urlApiGeocode + "?" + request);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            conn.setDoInput(true);
            conn.setRequestMethod("GET");

            if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
                    throw new GeoCodingConnectionException("Failed : HTTP error code : "
                            + conn.getResponseCode());
            }

            BufferedReader br = new BufferedReader(new InputStreamReader( (conn.getInputStream()), "UTF-8"));

            String line;
            StringBuilder output = new StringBuilder(); 
            while ((line = br.readLine()) != null) {
                    output.append(line);
            }

            conn.disconnect();
            
            return output.toString();

        } catch (MalformedURLException ex) {

            throw new GeoCodingConnectionException(ex);

        } catch (IOException ex) {

            throw new GeoCodingConnectionException(ex);

        }
    }
}
